package session

import (
	"errors"
	"geeorm/clause"
	"reflect"
)

//实现记录增删改查相关的代码
//多次调用clause.Set()构造好每一个子句
//最好调用一次clause.Build按照传入的顺序构造出最终的SQL语句，构造完成后，调用Raw().Exec()执行
func (s *Session)Insert(values ...interface{})(int64,error){
	recordValues:=make([]interface{},0)
	for _,value:=range values{
		s.CallMethod(BeforeInsert,value)
		table:=s.Model(value).RefTable()
		s.clause.Set(clause.INSERT,table.Name,table.FieldNames)
		recordValues=append(recordValues,table.RecordValues(value))
	}
	s.clause.Set(clause.VALUES,recordValues...)
	sql,vars:=s.clause.Build(clause.INSERT,clause.VALUES)
	result,err:=s.Raw(sql,vars...).Exec()
	if err!=nil{
		return 0,err
	}
	s.CallMethod(AfterInsert,nil)
	return result.RowsAffected()
}

//Find和Insert正好反了过来，Insert需要将已经存在的对象的每一个字段的值平铺开来，而Find则是根据
//需要平铺开的字段值构造出对象。统一也需要用到反射reflect
//Find代码实现比较复杂，主要分为以下几步
/*
	1,destSlice.Type().Elem()获取切片的单个元素的类型destType，用reflect.New（）方法创造
出一个destType的实例，作为Model（）的传入参，映射出表结构RefTable（）
	2，根据表结构，使用clause构造出SELECT语句，查询到所以符合条件的记录rows
	3，遍历每一行记录，利用反射创建出destType的实例dest，将dest的所有字段平铺开，构造切片values
	4，将dest添加到切片destSlice中，循环知道所有的记录都添加到切片destSlice中
*/
func (s *Session)Find(values interface{})error{
	s.CallMethod(BeforeQuery,nil)
	destSlice:=reflect.Indirect(reflect.ValueOf(values))
	destType:=destSlice.Type().Elem()
	table:=s.Model(reflect.New(destType).Elem().Interface()).RefTable()

	s.clause.Set(clause.SELECT,table.Name,table.FieldNames)
	sql,vars:=s.clause.Build(clause.SELECT,clause.WHERE,clause.ORDERBY,clause.LIMIT)
	rows,err:=s.Raw(sql,vars...).QueryRows()
	if err!=nil{
		return err
	}
	for rows.Next(){
		dest:=reflect.New(destType).Elem()
		var values []interface{}
		for _,name:=range table.FieldNames{
			values=append(values,dest.FieldByName(name).Addr().Interface())
		}
		if err:=rows.Scan(values...);err!=nil{
			return err
		}
		s.CallMethod(AfterQuery,dest.Addr().Interface())
		destSlice.Set(reflect.Append(destSlice,dest))
	}
	return rows.Close()
}

//Update方法比较特别的一点在于，Update接受2种入参，平铺开来的键值对和map类型的键值对。因为generator接受的是参数是map类型的键值对
//因此update方法会动态地判断传入参数地类型，如果不是map类型会自动转换
func (s *Session)Update(kv ...interface{})(int64,error){
	s.CallMethod(BeforeUpdate, nil)
	m,ok:=kv[0].(map[string]interface{})
	if !ok{
		m=make(map[string]interface{})
		for i:=0;i<len(kv);i+=2{
			m[kv[i].(string)]=kv[i+1]
		}
	}
	s.clause.Set(clause.UPDATE,s.RefTable().Name,m)
	sql,vars:=s.clause.Build(clause.UPDATE,clause.WHERE)
	result,err:=s.Raw(sql,vars...).Exec()
	if err!=nil{
		return 0,err
	}
	s.CallMethod(AfterUpdate, nil)
	return result.RowsAffected()
}

func (s *Session)Delete()(int64,error){
	s.CallMethod(BeforeDelete, nil)
	s.clause.Set(clause.DELETE,s.RefTable().Name)
	sql,vars:=s.clause.Build(clause.DELETE,clause.WHERE)
	result,err:=s.Raw(sql,vars...).Exec()
	if err!=nil{
		return 0,err
	}
	s.CallMethod(AfterDelete, nil)
	return result.RowsAffected()
}

func (s *Session)Count()(int64,error){
	s.clause.Set(clause.COUNT,s.RefTable().Name)
	sql,vars:=s.clause.Build(clause.COUNT,clause.WHERE)
	row:=s.Raw(sql,vars...).QueryRow()
	var tmp int64
	if err:=row.Scan(&tmp);err!=nil{
		return 0,err
	}
	return tmp,nil
}

//链式调用，某个对象调用某个方法后，将该对象地引用，/指针返回，即可以继续调用该对象地其他方法。通常来说，当某个对象需要一次调用多个方法
//设置属性时，就非常适合改造为链式调用
func (s *Session)Limit(num int)*Session{
	s.clause.Set(clause.LIMIT,num)
	return s
}

func (s *Session)Where(desc string,args ...interface{})*Session{
	var vars []interface{}
	s.clause.Set(clause.WHERE,append(append(vars,desc),args...)...)
	return s
}

func (s *Session)OrderBy(desc string)*Session{
	s.clause.Set(clause.ORDERBY,desc)
	return s
}

func (s *Session)First(value interface{})error{
	dest:=reflect.Indirect(reflect.ValueOf(value))
	destSlice:=reflect.New(reflect.SliceOf(dest.Type())).Elem()
	if err:=s.Limit(1).Find(destSlice.Addr().Interface());err!=nil{
		return err
	}
	if destSlice.Len()==0{
		return errors.New("NOT FOUND")
	}
	dest.Set(destSlice.Index(0))
	return nil
}
